/*
 * File:   main.cpp
 * Author: shadius
 *
 * Created on 29. říjen 2012, 19:21
 */

#include <cstdlib>
#include <string>
#include <iostream>
#include "Loader.h"
#include "EvoAlghorithm.h"
#include "Checker.h"
#include <memory>
#include "SVGGenerator.h"
#include "IFastDataApprox.h"
#include <array>
#include <boost/program_options.hpp>
#include <limits>
#include "StringUtils.h"
#include "INPUT_PARAMS.h"
#include "tbb/task_scheduler_init.h"

#define PARAM_HELP 1
#define PARAM_INPUT_FILE "input-file"
#define PARAM_THREAD "thread"
#define PARAM_THREAD_SHORT "thread,t"
#define PARAM_THREAD_DEFAULT 1
#define PARAM_DT "dtrange"
#define PARAM_DT_SHORT "dtrange,d"
#define PARAM_K "krange"
#define PARAM_K_SHORT "krange,k"
#define PARAM_C "cvalue"
#define PARAM_C_SHORT "cvalue,c"
#define PARAM_P "pvalue"
#define PARAM_P_SHORT "pvalue,p"
#define OUTPUT_POSTFIX ".svg"

using namespace std;

namespace po = boost::program_options;

struct ParamException: public std::exception {
	std::string s;
	ParamException(std::string ss) :
			s(ss) {
	}
	const char* what() const throw () {
		return s.c_str();
	}
	~ParamException() throw() {}
};

void printParameters(INPUT_PARAMS& params) {
	if (params.isPset)
		cout << "P: " << params.p << endl;
	if (params.isCset)
		cout << "C: " << params.c << endl;
	if (params.isKset)
		cout << "K: <" << params.kMin << ", " << params.kMax << ">" << endl;
	cout << "dt: <" <<  TimeUtils::convertWinToSec(params.dtMin) << ", " << TimeUtils::convertWinToSec(params.dtMax) << ">" << endl;
	cout << "Thread: " << params.threadCount << endl;

}

int handleParameters(po::variables_map& vm, po::options_description desc,
		INPUT_PARAMS& params) {
	if (vm.count("help")) {
		cout << desc << "\n";
		return PARAM_HELP;
	}

	if (vm.count(PARAM_INPUT_FILE)) {
		params.inputFile = vm[PARAM_INPUT_FILE].as<string>();
	} else {
		throw ParamException("missing input file parameter");
	}

	if (vm.count(PARAM_THREAD)) {
		params.threadCount = vm[PARAM_THREAD].as<int>();
	}
	if (vm.count(PARAM_DT)) {
		vector<int> dtRange = vm[PARAM_DT].as<vector<int>>();
		if (dtRange.size() != 2) {
			throw ParamException(
					"dt range wrong format. It should be like \"DTmin DTmax\" in seconds");
		}
		params.dtMin = TimeUtils::convertSecToWin(dtRange[0]);
		params.dtMax = TimeUtils::convertSecToWin(dtRange[1]);
		if (params.dtMin > params.dtMax) {
			throw ParamException(
					"dt range wrong format. Min is greater than max value.");
		}
	}
	if (vm.count(PARAM_K)) {
		vector<double> kRange = vm[PARAM_K].as<vector<double>>();
		if (kRange.size() != 2) {
			throw ParamException(
					"k range wrong format. It should be like \"DTmin DTmax\" in seconds");
		}
		params.kMin = kRange[0];
		params.kMax = kRange[1];
		params.isKset = true;
		if (params.kMin > params.kMax) {
			throw ParamException(
					"k range wrong format. Min is greater than max value.");
		}

	}
	if (vm.count(PARAM_C)) {
		params.c = vm[PARAM_C].as<double>();
		params.isCset = true;
	}

	if (vm.count(PARAM_P)) {
		params.p = vm[PARAM_P].as<double>();
		params.isPset = true;
	}
	return 0;
}

void compute(shared_ptr<IFastDataApprox> BGValues,
		shared_ptr<IFastDataApprox> IGValues, INPUT_PARAMS params) {
	int bgLength = 0;
	int igLength = 0;
	BGValues->Count(&bgLength);
	IGValues->Count(&igLength);
	int filled = -1;
	APPROXPOINT* bgAPPROXPOINTS = new APPROXPOINT[bgLength];
	APPROXPOINT* igAPPROXPOINTS = new APPROXPOINT[igLength];
	BGValues->GetBuf(bgAPPROXPOINTS, OneSecond, bgLength, &filled);
	cout << "bgLength count: " << bgLength << endl;
	cout << "FILLED count: " << filled << endl;
	IGValues->GetBuf(igAPPROXPOINTS, OneSecond, igLength, &filled);
	cout << "igLength count: " << igLength << endl;
	cout << "FILLED count: " << filled << endl;
	//dopředu vypočítat ECG hodnoty
	shared_ptr < vector<double> > p_ECGValues = make_shared < vector<double>
			> (filled);
	for (int i = 0; i < filled; i++) {
		(*p_ECGValues)[i] = bgAPPROXPOINTS[i].y - igAPPROXPOINTS[i].y;
	}
	EvoAlghorithm alg(BGValues, IGValues, p_ECGValues, params);
	INDIVIDUUM res = alg.compute(bgAPPROXPOINTS, igAPPROXPOINTS, filled);
	APPROXPOINT* ignewAPPROXPOINTS = new APPROXPOINT[filled];
	for (int j = 0; j < filled; j++) {
		ignewAPPROXPOINTS[j].x = TimeUtils::convertSecToWin(j);
		ignewAPPROXPOINTS[j].y = res.p * bgAPPROXPOINTS[j].y
				+ res.cg * bgAPPROXPOINTS[j].y * ((*p_ECGValues)[j]) + res.c;
	}
	SVGGenerator svgGen;
	StringUtils::replace(params.inputFile, ".", "");
	svgGen.draw(ignewAPPROXPOINTS, bgAPPROXPOINTS, igAPPROXPOINTS, filled,
			params.inputFile + OUTPUT_POSTFIX);
	std::cout << "EOL" << 2 << std::endl;
	delete[] bgAPPROXPOINTS;
	delete[] igAPPROXPOINTS;
	delete[] ignewAPPROXPOINTS;
}

int main(int argc, char** argv) {

	INPUT_PARAMS params = { "", 0, TimeUtils::convertSecToWin(PARAM_DT_DEFAULT_MIN),
			TimeUtils::convertSecToWin(PARAM_DT_DEFAULT_MAX), false, 0.0, 0.0, false, 0.0 };
	try {
		po::options_description desc("Configuration");
		desc.add_options()("help", "produce help message")(PARAM_THREAD_SHORT,
				po::value<int>()->default_value(PARAM_THREAD_DEFAULT),
				"set thread count")(PARAM_C_SHORT, po::value<double>(),
				"predefined c value")(PARAM_P_SHORT, po::value<double>(),
				"predefined p value")

		(PARAM_DT_SHORT, po::value<vector<int>>() -> multitoken(), "dt range, X Y format")(
				PARAM_K_SHORT, po::value<vector<double>>() -> multitoken(), "k range, X Y format")(
				PARAM_INPUT_FILE, po::value<string>(), "input file");
		po::positional_options_description p;
		p.add(PARAM_INPUT_FILE, -1);
		po::variables_map vm;
		po::store(
				po::command_line_parser(argc, argv).options(desc).positional(p).run(),
				vm);
		po::notify(vm);

		int handleRes = handleParameters(vm, desc, params);
		if (handleRes != 0) {
			if (handleRes == PARAM_HELP)
				return 0;
			else
				return handleRes;
		}
	} catch (ParamException& caught) {
		cout << "Parameter validation error: " << caught.what() << endl;
		return -1;
	} catch (exception& e) {
		cerr << "error: " << e.what() << "\n";
		return -1;
	} catch (...) {
		cerr << "Exception of unknown type!\n";
		return -1;
	}
	printParameters(params);
	Loader loader(params.inputFile);
	if (!loader.load()) {
		return -1;
	}

	shared_ptr<IFastDataApprox> BGValues = loader.getBGValues();
	shared_ptr<IFastDataApprox> IGValues = loader.getIGValues();
	tbb::task_scheduler_init init(params.threadCount);
	compute(BGValues, IGValues, params);
	init.terminate();
	return 0;
}

